home *** CD-ROM | disk | FTP | other *** search
/ Apple WWDC 1996 / WWDC96_1996 (CD).toast / Technology Materials / QuickTime VR / MacOS / QuickDraw™ 3D 1.0.6F4 SDK / Samples / SampleCode / 3DMF2PICT / 3DMF2PICTSupport.c < prev    next >
Encoding:
Text File  |  1996-02-20  |  17.7 KB  |  651 lines  |  [TEXT/MPCC]

  1. // Quickdraw 3D sample code
  2. //
  3. // This file illustrates how to set up a pixmap based draw context.
  4. // A metafile is read into and imaged in the pixmap, when the window is
  5. // closed the pixmap is used to generate a PICT of the scene.
  6. //
  7. // Nick Thompson, AppleLink: DEVSUPPORT (devsupport@applelink.apple.com)
  8. //
  9. // ©1994-5 Apple Computer Inc., All Rights Reserved
  10.  
  11.  
  12. #include <Files.h>
  13. #include <QuickDraw.h>
  14. #include <QDOffScreen.h>
  15. #include <StandardFile.h>
  16.  
  17. #include "3DMF2PICTSupport.h"
  18.  
  19. #include "QD3D.h"
  20. #include "QD3DDrawContext.h"
  21. #include "QD3DRenderer.h"
  22. #include "QD3DShader.h"
  23. #include "QD3DCamera.h"
  24. #include "QD3DLight.h"
  25. #include "QD3DGeometry.h"
  26. #include "QD3DGroup.h"
  27. #include "QD3DMath.h"
  28. #include "QD3DTransform.h"
  29. #include "QD3DStorage.h"
  30. #include "QD3DIO.h"
  31.  
  32.  
  33. //-----------------------------------------------------------------------------------------------
  34. // local utility functions
  35. static    TQ3FileObject         MyGetNewFile( FSSpec *myFSSpec, TQ3Boolean *isText ) ;
  36.  
  37. static void GetGroupBBox(
  38.     DocumentPtr            theDocument,
  39.     TQ3BoundingBox         *viewBBox) ;
  40.                                                 
  41. static    TQ3Status MyAddShaderToGroup( TQ3GroupObject group ) ;
  42.  
  43. static TQ3Status GetDocumentGroupBoundingBox( 
  44.     DocumentPtr theDocument , 
  45.     TQ3BoundingBox *viewBBox) ;
  46.  
  47. //-----------------------------------------------------------------------------------------------
  48. // Submit the scene for rendering/fileIO and picking
  49. TQ3Status SubmitScene( DocumentPtr theDocument ) 
  50. {        
  51.     TQ3Vector3D                globalScale;
  52.     TQ3Vector3D                globalTranslate;
  53.     
  54.     globalScale.x = globalScale.y = globalScale.z = theDocument->fGroupScale;
  55.     globalTranslate = *(TQ3Vector3D *)&theDocument->fGroupCenter;
  56.     Q3Vector3D_Scale(&globalTranslate, -1, &globalTranslate);
  57.     Q3Style_Submit(theDocument->fInterpolation, theDocument->fView);
  58.     Q3Style_Submit(theDocument->fBackFacing , theDocument->fView);
  59.     Q3Style_Submit(theDocument->fFillStyle, theDocument->fView);
  60.         
  61.     Q3MatrixTransform_Submit( &theDocument->fRotation, theDocument->fView);
  62.         
  63.     Q3ScaleTransform_Submit(&globalScale, theDocument->fView);
  64.     Q3TranslateTransform_Submit(&globalTranslate, theDocument->fView);
  65.     Q3DisplayGroup_Submit( theDocument->fModel, theDocument->fView);
  66.     
  67.     return kQ3Success ;
  68. }
  69.  
  70. //-----------------------------------------------------------------------------------------------
  71.  
  72. static TQ3Status GetDocumentGroupBoundingBox( 
  73.     DocumentPtr theDocument , 
  74.     TQ3BoundingBox *viewBBox)
  75. {
  76.     TQ3Status        status;
  77.     TQ3ViewStatus    viewStatus ;
  78.     
  79.     status = Q3View_StartBoundingBox( theDocument->fView, kQ3ComputeBoundsApproximate );
  80.     do {
  81.         status = SubmitScene( theDocument ) ;
  82.     } while((viewStatus = Q3View_EndBoundingBox( theDocument->fView, viewBBox )) == kQ3ViewStatusRetraverse );
  83.     return status ;
  84. }
  85. //-----------------------------------------------------------------------------------------------
  86.  
  87. TQ3ViewObject MyNewView(GWorldPtr myOffscreenGWorld)
  88. {
  89.     TQ3Status                myStatus;
  90.     TQ3ViewObject            myView;
  91.     TQ3DrawContextObject        myDrawContext;
  92.     TQ3RendererObject        myRenderer;
  93.     TQ3CameraObject            myCamera;
  94.     TQ3GroupObject            myLights;
  95.     
  96.     if((myView = Q3View_New()) == NULL)
  97.         goto bail ;    
  98.         
  99.     //    Create and set draw context.
  100.     if ((myDrawContext = MyNewDrawContext(myOffscreenGWorld)) == NULL )
  101.         goto bail;
  102.         
  103.     if ((myStatus = Q3View_SetDrawContext(myView, myDrawContext)) == kQ3Failure )
  104.         goto bail;
  105.  
  106.     Q3Object_Dispose( myDrawContext ) ;
  107.     
  108.     //    Create and set renderer.
  109.     // this would use the interactive software renderer
  110.  
  111.     if ((myRenderer = Q3Renderer_NewFromType(kQ3RendererTypeInteractive)) != NULL ) {
  112.         if ((myStatus = Q3View_SetRenderer(myView, myRenderer)) == kQ3Failure ) {
  113.             goto bail;
  114.         }
  115.     }
  116.     else {
  117.         goto bail;
  118.     }
  119.  
  120.     Q3Object_Dispose( myRenderer ) ;
  121.     
  122.     //    Create and set camera.
  123.     if ( (myCamera = MyNewCamera(myOffscreenGWorld)) == NULL )
  124.         goto bail;
  125.         
  126.     if ((myStatus = Q3View_SetCamera(myView, myCamera)) == kQ3Failure )
  127.         goto bail;
  128.  
  129.     Q3Object_Dispose( myCamera ) ;
  130.     
  131.     //    Create and set lights.
  132.     if ((myLights = MyNewLights()) == NULL )
  133.         goto bail;
  134.         
  135.     if ((myStatus = Q3View_SetLightGroup(myView, myLights)) == kQ3Failure )
  136.         goto bail;
  137.         
  138.     Q3Object_Dispose(myLights);
  139.  
  140.     //    Done!!!
  141.     return ( myView );
  142.     
  143. bail:
  144.     //    If any of the above failed, then don't return a view.
  145.     SysBeep(10) ;
  146.     return ( NULL );
  147. }
  148.  
  149. //----------------------------------------------------------------------------------
  150.  
  151. TQ3DrawContextObject MyNewDrawContext(GWorldPtr theGWorld)
  152. {
  153.     TQ3PixmapDrawContextData    myDrawContextData;
  154.     TQ3ColorARGB                clearColor ;
  155.     PixMapHandle                 hPixMap ;
  156.     Rect                        srcRect ;
  157.     
  158.     //    Set the background color.
  159.     clearColor.a = 1.0;
  160.     clearColor.r = 1.0;
  161.     clearColor.g = 1.0;
  162.     clearColor.b = 1.0;
  163.     
  164.     //    Fill in draw context data.
  165.             
  166.     myDrawContextData.drawContextData.clearImageMethod = kQ3ClearMethodWithColor;
  167.     myDrawContextData.drawContextData.clearImageColor  = clearColor;
  168.  
  169.     myDrawContextData.drawContextData.paneState = kQ3False;
  170.     myDrawContextData.drawContextData.maskState = kQ3False;
  171.     
  172.     myDrawContextData.drawContextData.doubleBufferState = kQ3False;
  173.  
  174.     hPixMap = GetGWorldPixMap(theGWorld);
  175.     LockPixels(hPixMap);
  176.  
  177.     srcRect = theGWorld->portRect;
  178.  
  179.     myDrawContextData.pixmap.width = srcRect.right  - srcRect.left;
  180.     myDrawContextData.pixmap.height= srcRect.bottom - srcRect.top;
  181.     
  182.     myDrawContextData.pixmap.rowBytes = (**hPixMap).rowBytes & 0x7FFF;
  183.     myDrawContextData.pixmap.pixelType = kQ3PixelTypeRGB32;
  184.     myDrawContextData.pixmap.pixelSize = 32;
  185.  
  186.     myDrawContextData.pixmap.bitOrder  = kQ3EndianBig;
  187.     myDrawContextData.pixmap.byteOrder = kQ3EndianBig;
  188.     
  189.     myDrawContextData.pixmap.image        = GetPixBaseAddr( hPixMap );
  190.     
  191.     return Q3PixmapDrawContext_New(&myDrawContextData);
  192. }
  193. //----------------------------------------------------------------------------------
  194.  
  195. TQ3CameraObject MyNewCamera(CGrafPtr thePort)
  196. {
  197.     TQ3CameraObject                    myCamera;
  198.     TQ3CameraData                    myCameraData;
  199.     TQ3ViewAngleAspectCameraData        myViewAngleCameraData;
  200.     TQ3Point3D                        cameraFrom     = { 0.0, 0.0, 30.0 };
  201.     TQ3Point3D                        cameraTo     = { 0.0, 0.0, 0.0 };
  202.     TQ3Vector3D                        cameraUp     = { 0.0, 1.0, 0.0 };
  203.     
  204.     float                             fieldOfView = .52359333333;
  205.     float                             hither         = 0.001;
  206.     float                             yon         = 1000;
  207.     
  208.     //    Fill in camera data.
  209.     myCameraData.placement.cameraLocation = cameraFrom;
  210.     myCameraData.placement.pointOfInterest = cameraTo;
  211.     myCameraData.placement.upVector = cameraUp;
  212.     
  213.     myCameraData.range.hither = hither;
  214.     myCameraData.range.yon = yon;
  215.     
  216.     myCameraData.viewPort.origin.x = -1.0;
  217.     myCameraData.viewPort.origin.y = 1.0;
  218.     myCameraData.viewPort.width = 2.0;
  219.     myCameraData.viewPort.height = 2.0;
  220.     
  221.     myViewAngleCameraData.cameraData = myCameraData;
  222.     myViewAngleCameraData.fov = fieldOfView ;
  223.     
  224.     // set up the aspect ratio based on the window
  225.     myViewAngleCameraData.aspectRatioXToY =  
  226.             (float) (thePort->portRect.right - thePort->portRect.left) / 
  227.             (float) (thePort->portRect.bottom - thePort->portRect.top);
  228.  
  229.     myCamera = Q3ViewAngleAspectCamera_New(&myViewAngleCameraData);    
  230.     
  231.     //    Return the camera.
  232.     return ( myCamera );
  233. }
  234.  
  235.  
  236. //----------------------------------------------------------------------------------
  237.  
  238. TQ3GroupObject MyNewLights()
  239. {
  240.     TQ3GroupPosition            myGroupPosition;
  241.     TQ3GroupObject            myLightList;
  242.     TQ3LightData                myLightData;
  243.     TQ3PointLightData        myPointLightData;
  244.     TQ3DirectionalLightData    myDirectionalLightData;
  245.     TQ3LightObject            myAmbientLight, myPointLight, myFillLight;
  246.     TQ3Point3D                pointLocation = { -10.0, 0.0, 10.0 };
  247.     TQ3Vector3D                fillDirection = { 10.0, 0.0, 10.0 };
  248.     TQ3ColorRGB                WhiteLight = { 1.0, 1.0, 1.0 };
  249.     
  250.     //    Set up light data for ambient light.  This light data will be used for point and fill
  251.     //    light also.
  252.  
  253.     myLightData.isOn = kQ3True;
  254.     myLightData.color = WhiteLight;
  255.     
  256.     //    Create ambient light.
  257.     myLightData.brightness = .2;
  258.     myAmbientLight = Q3AmbientLight_New(&myLightData);
  259.     if ( myAmbientLight == NULL )
  260.         goto bail;
  261.     
  262.     //    Create point light.
  263.     myLightData.brightness = 1.0;
  264.     myPointLightData.lightData = myLightData;
  265.     myPointLightData.castsShadows = kQ3False;
  266.     myPointLightData.attenuation = kQ3AttenuationTypeNone;
  267.     myPointLightData.location = pointLocation;
  268.     myPointLight = Q3PointLight_New(&myPointLightData);
  269.     if ( myPointLight == NULL )
  270.         goto bail;
  271.  
  272.     //    Create fill light.
  273.     myLightData.brightness = .2;
  274.     myDirectionalLightData.lightData = myLightData;
  275.     myDirectionalLightData.castsShadows = kQ3False;
  276.     myDirectionalLightData.direction = fillDirection;
  277.     myFillLight = Q3DirectionalLight_New(&myDirectionalLightData);
  278.     if ( myFillLight == NULL )
  279.         goto bail;
  280.  
  281.     //    Create light group and add each of the lights into the group.
  282.     myLightList = Q3LightGroup_New();
  283.     if ( myLightList == NULL )
  284.         goto bail;
  285.     myGroupPosition = Q3Group_AddObject(myLightList, myAmbientLight);
  286.     if ( myGroupPosition == 0 )
  287.         goto bail;
  288.     myGroupPosition = Q3Group_AddObject(myLightList, myPointLight);
  289.     if ( myGroupPosition == 0 )
  290.         goto bail;
  291.     myGroupPosition = Q3Group_AddObject(myLightList, myFillLight);
  292.     if ( myGroupPosition == 0 )
  293.         goto bail;
  294.  
  295.     Q3Object_Dispose( myAmbientLight ) ;
  296.     Q3Object_Dispose( myPointLight ) ;
  297.     Q3Object_Dispose( myFillLight ) ;
  298.  
  299.     //    Done!
  300.     return ( myLightList );
  301.     
  302. bail:
  303.     //    If any of the above failed, then return nothing!
  304.     return ( NULL );
  305. }
  306.  
  307. //----------------------------------------------------------------------------------
  308.  
  309. TQ3GroupObject MyNewModelFromFile(FSSpec *theFileSpec)
  310. {
  311.     TQ3GroupObject        myGroup = NULL ;
  312.     TQ3Boolean            isText = kQ3False ;
  313.     TQ3FileMode            myFileMode = kQ3FileModeNormal ; // we are reading the file
  314.     TQ3FileObject        theFile = NULL;
  315.     
  316.     //    Create a ordered group for the complete model.
  317.     if ((myGroup = Q3OrderedDisplayGroup_New()) == NULL )
  318.         return NULL;
  319.  
  320.     theFile = MyGetNewFile( theFileSpec, &isText ) ;
  321.     
  322.     if( isText == kQ3True )
  323.         myFileMode |= kQ3FileModeText;    // is it a text metafile??    
  324.  
  325.     // Open the file object
  326.     if( Q3File_OpenRead( theFile, &myFileMode ) != kQ3Success)
  327.         return  NULL ;
  328.  
  329.     MyReadModelFromFile( theFile, myGroup ) ;
  330.     
  331.     Q3File_Close(theFile);            // close and dispose of the file object
  332.     Q3Object_Dispose(theFile);
  333.     
  334.     MyAddShaderToGroup( myGroup ) ;
  335.     
  336.     return myGroup ;
  337. }
  338.  
  339.  
  340. //----------------------------------------------------------------------------------
  341. // attach a shader to the group
  342.  
  343. TQ3Status MyAddShaderToGroup( TQ3GroupObject group )
  344. {
  345.     TQ3ShaderObject    illuminationShader = Q3PhongIllumination_New();
  346.  
  347.     Q3Group_AddObject(group, illuminationShader);
  348.     Q3Object_Dispose(illuminationShader);
  349.     return(kQ3Success);
  350. }
  351.  
  352. //----------------------------------------------------------------------------------
  353. // read model from file object into the supplied group
  354.  
  355. TQ3Status MyReadModelFromFile( TQ3FileObject theFile,TQ3GroupObject myGroup)
  356. {    
  357.     if(theFile != NULL) {
  358.     
  359.         TQ3Object            myTempObj ;
  360.         TQ3Boolean            isEOF ;
  361.                 
  362.     
  363.         // read objects from the file
  364.         do {
  365.         
  366.             myTempObj = Q3File_ReadObject( theFile );
  367.             
  368.             if( myTempObj != NULL ) {
  369.                 // we only want the object in our main group if we can draw it
  370.                 if (Q3Object_IsDrawable( myTempObj) ) 
  371.                     Q3Group_AddObject( myGroup, myTempObj ) ;
  372.                 
  373.                 // we either added the object to the main group, or we don't care
  374.                 // so we can safely dispose of the object
  375.                 Q3Object_Dispose( myTempObj ) ;
  376.             }
  377.             
  378.             // check to see if we reached the end of file yet
  379.             isEOF = Q3File_IsEndOfFile( theFile );
  380.             
  381.         } while (isEOF == kQ3False);    
  382.     }
  383.     
  384.     if( myGroup != NULL )
  385.         return kQ3Success ;
  386.     else
  387.         return kQ3Failure ;
  388. }
  389.  
  390. //-----------------------------------------------------------------------------------------------
  391. // cleaned up from IM QuickDraw 3D pp 15-5
  392. static TQ3FileObject MyGetNewFile( FSSpec *myFSSpec, TQ3Boolean *isText )
  393. {
  394.     TQ3FileObject        myFileObj;
  395.     TQ3StorageObject        myStorageObj;
  396.     OSType                myFileType;
  397.     
  398.     FInfo                fndrInfo ;
  399.  
  400.     // we assume the FSSpec passed in was valid, get the file information
  401.     // we need to know the file type, this routine may get called by an appleEvent
  402.     // handler, so we can't assume a type, we need to get it from the fsspec.
  403.     
  404.     FSpGetFInfo( myFSSpec, &fndrInfo ) ;
  405.     
  406.     // pull out the file type
  407.     
  408.     myFileType = fndrInfo.fdType ;
  409.     
  410.     // Create new storage object and new file object 
  411.     if(((myStorageObj = Q3FSSpecStorage_New( myFSSpec )) == NULL) 
  412.         || ((myFileObj = Q3File_New()) == NULL)) 
  413.     {
  414.         if (myStorageObj != NULL) 
  415.             Q3Object_Dispose(myStorageObj);
  416.         return(NULL);
  417.     }
  418.  
  419.     // Set the storage for the file object
  420.     Q3File_SetStorage(myFileObj, myStorageObj);
  421.     Q3Object_Dispose(myStorageObj);
  422.  
  423.     if (myFileType == '3DMF')
  424.         *isText = kQ3False ;
  425.     else if (myFileType == 'TEXT')
  426.         *isText = kQ3True ;
  427.  
  428.     return (myFileObj);
  429. }
  430.  
  431.  
  432. //-------------------------------------------------------------------------------------------
  433. //
  434. Boolean MetafileFileSpecify( FSSpec *theFile )
  435. {
  436.     StandardFileReply    theSFReply ;
  437.     SFTypeList            myTypes = { '3DMF' } ;
  438.     const short            numTypes = 1 ;
  439.         
  440.     // Get the file name to open
  441.     StandardGetFile( nil, numTypes, myTypes, &theSFReply ) ;
  442.     
  443.     if( theSFReply.sfGood )
  444.         *theFile = theSFReply.sfFile ;
  445.     
  446.     // did the user cancel?
  447.     return theSFReply.sfGood ;
  448.     
  449. }
  450. //----------------------------------------------------------------------------------
  451.  
  452.  
  453. void GetGroupBBox(
  454.     DocumentPtr            theDocument,
  455.     TQ3BoundingBox         *viewBBox)
  456. {
  457.     TQ3Point3D                     from     = { 0.0, 0.0, 1.0 };
  458.     TQ3Point3D                     to         = { 0.0, 0.0, 0.0 };
  459.     TQ3Vector3D                     up         = { 0.0, 1.0, 0.0 };
  460.     
  461.     float                         fieldOfView = .52359333333;
  462.     float                         hither         =  0.5;
  463.     float                         yon         =  1.5;
  464.     TQ3GroupObject                mainGroup = theDocument->fModel ;
  465.  
  466.     TQ3Status                    status;
  467.     
  468. #ifdef BETA_1_BUILD
  469.     Q3View_StartBounds( theDocument->fView );
  470.  
  471.     status = Q3DisplayGroup_BoundingBox(mainGroup, 
  472.                                         viewBBox, 
  473.                                         kQ3ComputeBoundsApproximate,
  474.                                          viewObject);
  475.  
  476.     Q3View_EndBounds( theDocument->fView );
  477. #else
  478.     status = GetDocumentGroupBoundingBox( theDocument , viewBBox) ;
  479. #endif
  480.                                         
  481.     //
  482.     //  If we have a point model, then the "viewBBox" would end up
  483.     //  being a "singularity" at the location of the point.  As
  484.     //  this bounding "box" is used in setting up the camera spec,
  485.     //  we get bogus input into Escher.
  486.     
  487.     {
  488.          float        xSize, ySize, zSize;
  489.         
  490.         xSize = viewBBox->max.x - viewBBox->min.x;
  491.         ySize = viewBBox->max.y - viewBBox->min.y;
  492.         zSize = viewBBox->max.z - viewBBox->min.z;
  493.  
  494.         if (xSize <= kQ3RealZero &&
  495.             ySize <= kQ3RealZero &&
  496.             zSize <= kQ3RealZero) {
  497.             
  498.             viewBBox->max.x += 0.0001;
  499.             viewBBox->max.y += 0.0001;
  500.             viewBBox->max.z += 0.0001;
  501.             
  502.             viewBBox->min.x -= 0.0001;
  503.             viewBBox->min.y -= 0.0001;
  504.             viewBBox->min.z -= 0.0001;
  505.         }
  506.     }
  507. }
  508.  
  509.  
  510.  
  511.  
  512. //------------------------------------------------------------------------
  513.  
  514.  
  515. TQ3Point3D AdjustCamera(
  516.     DocumentPtr            theDocument,
  517.     short                winWidth,
  518.     short                winHeight)
  519. {
  520.     float                         fieldOfView;
  521.     float                         hither;
  522.     float                         yon;
  523.     TQ3CameraPlacement            placement;
  524.     TQ3CameraRange                range;
  525.     TQ3BoundingBox                 viewBBox;
  526.     long                         fromAxis;    
  527.     float                         maxDimension;
  528.      float                        xSize, ySize, zSize;
  529.     float                        weights[2] = { 0.5, 0.5 };
  530.     TQ3Point3D                    points[2];
  531.     TQ3Vector3D                     viewVector;
  532.     TQ3Vector3D                    normViewVector;
  533.     TQ3Vector3D                    eyeToFrontClip;
  534.     TQ3Vector3D                    eyeToBackClip;
  535.     float                        viewDistance;
  536.     TQ3Vector3D                    diagonalVector;
  537.     float                        ratio;
  538.     TQ3CameraObject                camera;
  539.     
  540.     TQ3ViewObject                theView = theDocument->fView ;
  541.     TQ3GroupObject                mainGroup = theDocument->fModel ;
  542.     
  543.     TQ3Point3D                    *documentGroupCenter = &theDocument->fGroupCenter ;
  544.     float                        *documentGroupScale  = &theDocument->fGroupScale ;
  545.  
  546.     Q3View_GetCamera( theView, &camera);
  547.     GetGroupBBox( theDocument, &viewBBox);
  548.  
  549.     /*
  550.      *  If we have a point model, then the "viewBBox" would end up
  551.      *  being a "singularity" at the location of the point.  As
  552.      *  this bounding "box" is used in setting up the camera spec,
  553.      *  we get bogus input into Escher.
  554.      */
  555.     xSize = viewBBox.max.x - viewBBox.min.x;
  556.     ySize = viewBBox.max.y - viewBBox.min.y;
  557.     zSize = viewBBox.max.z - viewBBox.min.z;
  558.  
  559.     if (xSize <= kQ3RealZero &&
  560.         ySize <= kQ3RealZero &&
  561.         zSize <= kQ3RealZero)  {
  562.         viewBBox.max.x += 0.0001;
  563.         viewBBox.max.y += 0.0001;
  564.         viewBBox.max.z += 0.0001;
  565.         
  566.         viewBBox.min.x -= 0.0001;
  567.         viewBBox.min.y -= 0.0001;
  568.         viewBBox.min.z -= 0.0001;
  569.     }
  570.  
  571.     points[0] = viewBBox.min;
  572.     points[1] = viewBBox.max;
  573.  
  574.     Q3Point3D_AffineComb(points, weights, 2, documentGroupCenter);
  575.  
  576.     /*
  577.      *  The "from" point is on a vector perpendicular to the plane
  578.      *  in which the bounding box has greatest dimension.  As "up" is
  579.      *  always in the positive y direction, look at x and z directions.
  580.      */
  581.     xSize = viewBBox.max.x - viewBBox.min.x;
  582.     zSize = viewBBox.max.z - viewBBox.min.z;
  583.     
  584.     if (xSize > zSize) {
  585.         fromAxis = kQ3AxisZ;
  586.     } else {
  587.         fromAxis = kQ3AxisX;
  588.     }
  589.  
  590.     /*
  591.      *  Compute the length of the diagonal of the bounding box.
  592.      *
  593.      *  The hither and yon planes are adjusted so that the
  594.       *  diagonal of the bounding box is 7/8 the size of the
  595.       *  minimum dimension of the view frustum. The diagonal is used instead
  596.       *  of the maximum size (in x, y, or z) so that when you rotate
  597.       *  the object, the corners don't get clipped out.
  598.       */
  599.     Q3Point3D_Subtract(
  600.         &viewBBox.max,
  601.         &viewBBox.min,
  602.         &diagonalVector);
  603.  
  604.     maxDimension    =    Q3Vector3D_Length(&diagonalVector);
  605.     maxDimension    *=    8.0 / 7.0;
  606.     
  607.     ratio = 1.0 / maxDimension;
  608.             
  609.     *documentGroupScale = ratio;
  610.     
  611.     Q3Camera_GetPlacement(camera, &placement);
  612.  
  613.     Q3Point3D_Subtract(
  614.         &placement.cameraLocation,
  615.         &placement.pointOfInterest,
  616.         &viewVector);
  617.         
  618.     viewDistance = Q3Vector3D_Length(&viewVector);
  619.     
  620.     Q3Vector3D_Normalize(&viewVector, &normViewVector);
  621.     
  622.     Q3Vector3D_Scale(&normViewVector, 
  623.                      viewDistance - ratio * maxDimension/2.0,
  624.                      &eyeToFrontClip);
  625.                     
  626.     Q3Vector3D_Scale(&normViewVector, 
  627.                     viewDistance + ratio * maxDimension/2.0,
  628.                     &eyeToBackClip);
  629.  
  630.     hither     = Q3Vector3D_Length(&eyeToFrontClip);
  631.     yon     = Q3Vector3D_Length(&eyeToBackClip);
  632.     
  633.     fieldOfView = 2 * atan((ratio * maxDimension/2.0)/hither);
  634.  
  635.     range.hither                 = hither;
  636.     range.yon                     = yon;
  637.  
  638.     Q3Camera_SetRange(camera, &range);
  639.  
  640.     Q3ViewAngleAspectCamera_SetFOV(
  641.         camera, fieldOfView);
  642.  
  643.     Q3ViewAngleAspectCamera_SetAspectRatio(
  644.         camera, (float) winWidth / (float) winHeight);
  645.  
  646.     Q3Object_Dispose(camera);
  647.     
  648.     return( *documentGroupCenter );
  649. }
  650.  
  651.